<template>
	<div class="app">
		<input
			v-model="text"
			style="width:700px"
			@keydown.enter="test"
		>
		<button @click="test">测试</button>
		<div>{{format}}</div>
	</div>
</template>

<script setup>
import {shallowRef} from 'vue';
console.log('app');
function getAst(text,noCheck){
	const reg=/\{(.+?)\}|(\d+(?:\.\d+)?)|(or|and)|([<>=≠≥≤])|([-+*/])|(求和|列统计)|(,)|[()]/g;
	const tokens=[];
	const statement={
		type:'ExpressionStatement',
		expression:null,
		index:0,
		start:0,
		end:0,
		raw:text,
		tokens,
		//region 遍历方法
		traverser(visitor){
			function traverseNode(node,parent){
				if(node==null){
					return;
				}
				const methods=visitor[node.type];
				if(methods&&methods.enter){
					methods.enter(node,parent);
				}
				if(node.type==='ExpressionStatement'||node.type==='ParenExpression'){
					traverseNode(node.expression,node);
				}else if(node.type==='BinaryExpression'||node.type==='LogicalExpression'||node.type==='RelationExpression'){
					traverseNode(node.left,node);
					traverseNode(node.right,node);
				}else if(node.type==='CallExpression'){
					for(const arg of node.arguments){
						traverseNode(arg,node);
					}
				}
				if(methods&&methods.exit){
					methods.exit(node,parent);
				}
			}
			traverseNode(this,null);
		},
		//endregion
	};
	let result;
	let lastIndex=0;
	let current=0;
	//region 错误信息
	function throwError(info,pos,code){
		if(!noCheck){
			// eslint-disable-next-line no-throw-literal
			throw `${info}[位置:${pos}]相关代码: ${code}`;
		}
	}
	function errorStr({index}){
		let {start,end}=tokens[index];
		if(index>0){
			start=tokens[index-1].start;
		}
		if(index<tokens.length-1){
			end=tokens[index+1].end;
		}
		return text.slice(start,end);
	}
	//endregion
	//region 挂载节点
	function nodeMounted(parent,node,force){
		if(parent.type==='ExpressionStatement'||parent.type==='ParenExpression'){
			if(force||parent.expression==null){
				parent.expression=node;
				return;
			}
		}else if(parent.type==='BinaryExpression'||parent.type==='LogicalExpression'||parent.type==='RelationExpression'){
			if(force||parent.right==null){
				parent.right=node;
				return;
			}
		}else if(parent.type==='CallExpression'){
			if(force||parent.tempArg==null){
				parent.tempArg=node;
				return;
			}
		}
		throwError('语法错误1',node.start,errorStr(node));
	}
	//endregion
	//region 根据优先级寻找节点和上下文
	function findNodeByPriority(context,priority){
		let node=context;
		do{
			context=node;
			if(node.type==='ExpressionStatement'||node.type==='ParenExpression'){
				node=node.expression;
			}else if(node.type==='BinaryExpression'||node.type==='LogicalExpression'||node.type==='RelationExpression'){
				node=node.right;
			}else if(node.type==='CallExpression'){
				node=node.tempArg;
			}
			if(node==null){
				throwError('运算符周围必须是数字或者变量',context.start,errorStr(context));
				return;
			}
		}while(node.priority!=null&&priority>node.priority);
		return {context,node};
	}
	//endregion
	//region 语法分析
	function parseStatement(parent){
		let token=tokens[current];
		if(token==null){
			token=tokens[current-1];
			throwError('意外结束',token.start,errorStr(token));
		}
		if(token.type==='Identifier'||token.type==='Literal'){
			nodeMounted(parent,token);
			++current;
		}else if(token.type==='logic'||token.type==='compare'||token.type==='operator'){
			const {context,node:preNode}=findNodeByPriority(parent,token.priority);
			const node={
				type:token.type==='logic'?'LogicalExpression':token.type==='compare'?'RelationExpression':'BinaryExpression',
				operator:token.value,
				valueType:token.type==='operator'?'literal':'boolean',
				priority:token.priority,
				left:preNode,
				right:null,
				index:current,
				start:preNode.start,
				end:null,
			};
			++current;
			parseStatement(node);
			nodeMounted(context,node,true);
		}else if(token.type==='function'){
			const node={
				type:'CallExpression',
				callee:token.value,
				valueType:'literal',
				arguments:[],
				index:current,
				start:token.start,
				end:null,
				tempArg:null,
			};
			if(tokens[++current]==null||tokens[current].type!=='paren'||tokens[current].value!=='('){
				throwError(node.callee+', 缺少实参列表',token.start,errorStr(token));
			}
			token=tokens[++current];
			// eslint-disable-next-line no-constant-condition
			while(true){
				if(token==null){
					throwError(node.callee+', 不完整的实参列表',node.start,errorStr(node));
				}
				if(token.type==='paren'&&token.value===')'){
					break;
				}
				parseStatement(node);
				token=tokens[current];
			}
			if(node.tempArg!=null){
				node.arguments.push(node.tempArg);
			}
			delete node.tempArg;
			node.end=token.end;
			if(tokens[current-1].type==='comma'){
				throwError('无效实参',token.start,errorStr(token));
			}
			nodeMounted(parent,node);
			++current;
		}else if(token.type==='paren'&&token.value==='('){
			const node={
				type:'ParenExpression',
				expression:null,
				index:current,
				start:token.start,
				end:null,
			};
			token=tokens[++current];
			// eslint-disable-next-line no-constant-condition
			while(true){
				if(token==null){
					throwError('未匹配的左括号',node.start,errorStr(node));
				}
				if(token.type==='paren'&&token.value===')'){
					break;
				}
				parseStatement(node);
				token=tokens[current];
			}
			node.end=token.end;
			nodeMounted(parent,node);
			++current;
		}else if(token.type==='comma'){
			if(parent.type!=='CallExpression'){
				throwError('意外的逗号',token.start,errorStr(token));
			}
			if(parent.tempArg==null){
				throwError('无效实参',token.start,errorStr(token));
			}
			parent.arguments.push(parent.tempArg);
			parent.tempArg=null;
			++current;
		}else{
			throwError('未匹配的右括号',token.start,errorStr(token));
		}
	}
	//endregion
	try{
		//region 词法分析
		let index=0;
		// eslint-disable-next-line no-cond-assign
		while(result=reg.exec(text)){
			let token;
			const start=result.index;
			const end=start+result[0].length;
			if(result[1]!==undefined){
				token={
					type:'Identifier',
					value:result[1],
					valueType:'literal',
					index,
					start,
					end,
				};
			}else if(result[2]!==undefined){
				token={
					type:'Literal',
					value:result[2],
					valueType:'literal',
					index,
					start,
					end,
				};
			}else if(result[3]!==undefined){
				token={
					type:'logic',
					value:result[3],
					priority:result[3]==='or'?1:2,
					index,
					start,
					end,
				};
			}else if(result[4]!==undefined){
				token={
					type:'compare',
					value:result[4],
					priority:3,
					index,
					start,
					end,
				};
			}else if(result[5]!==undefined){
				token={
					type:'operator',
					value:result[5],
					priority:result[5]==='+'||result[5]==='-'?4:5,
					index,
					start,
					end,
				};
			}else if(result[6]!==undefined){
				token={
					type:'function',
					value:result[6],
					index,
					start,
					end,
				};
			}else if(result[7]!==undefined){
				token={
					type:'comma',
					value:result[7],
					index,
					start,
					end,
				};
			}else{
				token={
					type:'paren',
					value:result[0],
					index,
					start,
					end,
				};
			}
			++index;
			tokens.push(token);
			if(lastIndex!==start){
				const noParse=text.slice(lastIndex,start).trim();
				if(noParse!==''){
					throwError('无法解析的字符',lastIndex,noParse);
				}
			}
			lastIndex=reg.lastIndex;
		}
		const noParse=text.slice(lastIndex).trim();
		if(noParse!==''){
			throwError('无法解析的字符',lastIndex,noParse);
		}
		//endregion
		if(tokens.length){
			statement.start=tokens[0].start;
			statement.end=tokens[tokens.length-1].end;
		}
		while(current<tokens.length){
			parseStatement(statement);
		}
		//region 类型校验及定end
		statement.traverser({
			ExpressionStatement:{
				exit(node){
					if(node.expression){
						node.valueType=node.expression.valueType;
					}
				},
			},
			ParenExpression:{
				exit(node){
					if(node.expression==null){
						throwError('无效空括号',node.start,errorStr(node));
					}else if(node.expression.type==='Identifier'||node.expression.type==='Literal'){
						throwError('多余的括号',node.start,text.slice(node.start,node.end));
					}
					node.valueType=node.expression.valueType;
				},
			},
			CallExpression:{
				exit(node){
					if(node.arguments.length<2){
						throwError(node.callee+', 至少需要2个实参',node.start,text.slice(node.start,node.end));
					}
					for(const arg of node.arguments){
						if(arg.type!=='Identifier'){
							throwError(node.callee+', 只支持变量作为实参',arg.start,text.slice(arg.start,arg.end));
						}
					}
				},
			},
			LogicalExpression:{
				exit(node){
					node.end=node.right.end;
					if(node.left.valueType!=='boolean'){
						throwError(node.operator+'左侧不是boolean类型',node.left.start,text.slice(node.left.start,node.left.end));
					}
					if(node.right.valueType!=='boolean'){
						throwError(node.operator+'右侧不是boolean类型',node.right.start,text.slice(node.right.start,node.right.end));
					}
				},
			},
			RelationExpression:{
				exit(node){
					node.end=node.right.end;
					if(node.left.valueType!==node.right.valueType){
						throwError(node.operator+'两侧类型不同',node.start,text.slice(node.start,node.end));
					}
					if(node.operator!=='='&&node.operator!=='≠'&&node.left.valueType==='boolean'){
						throwError('boolean类型的只支持=或≠关系运算符',node.start,text.slice(node.start,node.end));
					}
				},
			},
			BinaryExpression:{
				exit(node){
					node.end=node.right.end;
					if(node.left.valueType!=='literal'){
						throwError(node.operator+'左侧必须是非boolean类型',node.left.start,text.slice(node.left.start,node.left.end));
					}
					if(node.right.valueType!=='literal'){
						throwError(node.operator+'右侧必须是非boolean类型',node.right.start,text.slice(node.right.start,node.right.end));
					}
				},
			},
		});
		//endregion
	}catch(err){
		statement.error=err;
	}
	return statement;
}

const text=shallowRef('{7月4日(迟到)}+3-({全部字段}*20+30)≠10and{7月6日(迟到)}+10≥{7月8日(缺勤)}or1+3=5+求和({测试1},{测试2})');
const format=shallowRef('');
function test(){
	const ast=getAst(text.value);
	if(ast.error){
		console.error(ast.error);
	}
	console.log(ast);
	format.value=codeGenerator(ast);
	function codeGenerator(node){
		if(node==null){
			return '';
		}else if(node.type==='ExpressionStatement'){
			return codeGenerator(node.expression);
		}else if(node.type==='ParenExpression'){
			return '('+codeGenerator(node.expression)+')';
		}else if(node.type==='BinaryExpression'||node.type==='LogicalExpression'||node.type==='RelationExpression'){
			return codeGenerator(node.left)+' '+node.operator+' '+codeGenerator(node.right);
		}else if(node.type==='CallExpression'){
			return node.callee+'('+node.arguments.map((arg) => {
				return codeGenerator(arg);
			}).join(', ')+')';
		}else if(node.type==='Identifier'){
			return '{'+node.value+'}';
		}else if(node.type==='Literal'){
			return node.value;
		}
	}
}
</script>